﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

//using SharpMap.Geometries;
//using SharpMap.Converters.WellKnownText;
//using ProjNet.CoordinateSystems;
//using ProjNet.CoordinateSystems;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using SpectationClient.DataBaseDescription;
using Npgsql;

namespace SpectationClient.SQLCommandBuilder {
   // public class GeometricCondition : ICondition {
    public class GeometricCondition: AbstractCondition {
   
        public enum Op: byte { 
            //comparision on the column's geometrySQL only -> no data value requested
            IS_NULL,
            IS_CLOSED,
            IS_EMPTY,
            IS_RING,
            IS_SIMPLE,
            IS_VALID,
            //needs one geometrySQL to be compared with the column's geometriy
            IS_WITHIN,
            TOUCHES,
            OVERLAPS,
            INTERSECTS,
            HAS_ARC,
            EUQALS,
            DISJOINT,
        }
        private enum nGeometriesRequired: byte {
            zero,
            one
        }

        public override string ToString() {
            return this.column;
        }

        private nGeometriesRequired maxGeometriesRequired;
        private String column;
        private long columnSRID;
        private Op conditionOperator;
        private IGeometry geometry2compare;
        private long geometry2compare_srid;
        private Double buffer_distance = 0;
        private bool negation = false;
        private static NetTopologySuite.IO.WKBWriter WKBWriter = new NetTopologySuite.IO.WKBWriter();

        public GeometricCondition(String column, long columnSRID, GeometricCondition.Op op) {
            init(column, columnSRID, op, false);
        }
        
        public GeometricCondition(String column, long columnSRID, GeometricCondition.Op op, bool negation) {
            init(column, columnSRID, op, negation);
        }
        
        public GeometricCondition(ColumnInfoGeometry gi, GeometricCondition.Op op, bool negation) {
            init(gi.Name, gi.SRID, op, negation);
        }
        public GeometricCondition(ColumnInfoGeometry gi, GeometricCondition.Op op) {
            init(gi.Name, gi.SRID, op, false);
        }

        private void init(String columnName, long columnSRID, GeometricCondition.Op op, bool negation){
            if(columnName == null || columnName.Trim().Length == 0) throw new Exception("missing column name");
            this.conditionOperator = op;
            if(columnName.ToLower() != columnName) {
                this.column = String.Format("\"{0}\"", columnName);
            } else {
                this.column = columnName;
            }
            
            this.columnSRID = columnSRID;
            this.maxGeometriesRequired = GeometricCondition.get_nGeometriesRequired(op);
            this.negation = negation;
        }


        private static nGeometriesRequired get_nGeometriesRequired(Op op) {
            switch(op) { 
                case Op.IS_NULL:
                case Op.IS_CLOSED:
                case Op.IS_EMPTY:
                case Op.IS_RING:
                case Op.IS_SIMPLE:
                case Op.IS_VALID: return nGeometriesRequired.zero;
                default: return nGeometriesRequired.one;
            }
        }

        public static long getPostGIS_SRID(ref DataTable spatial_ref_sys, string auth_name, long auth_srid) {

            foreach(DataRow row in spatial_ref_sys.Rows) {
                if(Object.Equals(row["auth_name"], auth_name) &&
                   Object.Equals(row["auth_srid"], auth_srid)) return (long)row["columnSRID"];

            }
            throw new Exception(String.Format("No PostGIS columnSRID found for Authority {0} and Authority columnSRID {1}",
                auth_name, auth_srid));
            //return -1;
        }

        /// <summary>
        /// Sets the buffer parameters around the geometrySQL that is to be compared with a tables' geometrySQL.
        /// </summary>
        /// <param name="bufferDistance"></param>
        public void SetBuffer(Double bufferDistance) {
            if(this.maxGeometriesRequired == nGeometriesRequired.zero) {
                throw new Exception("Buffering can be applied only on a geometrySQL for comparision");
            } else {
                this.buffer_distance = bufferDistance;
            }
            
        }

       // #region ICondition Member
        public void Add(Double x_lon, Double y_lat) {
            this.Add(x_lon, y_lat, this.columnSRID);
        }

        public void Add(Double x_lon, Double y_lat, long srid) {
           this.Add(new Point(x_lon, y_lat), srid);
        }

        public void Add(IPoint point) {
            this.Add(point, this.columnSRID);
        }
        public void Add(IPoint point, long srid) {
            this.geometry2compare = point;
            this.geometry2compare_srid = srid;
        }
        
        public void Add(ILineString line) {
            this.Add(line, this.columnSRID);
        }
        
        public void Add(ILineString line, long srid) {
            this.geometry2compare = line;
            this.geometry2compare_srid = srid;
        }
        public void Add(IPolygon polygon, long srid) {
            this.geometry2compare = polygon;
            this.geometry2compare_srid = srid;
        }

        public void Add(IPolygon polygon) {
            this.Add(polygon, this.columnSRID);
        }

        public void Add(IGeometry geometry) {
            this.Add(geometry, this.columnSRID);
        }

        public void Add(IGeometry geometry, long srid) {
            this.geometry2compare = geometry;
            this.geometry2compare_srid = srid;
        }


        public override NpgsqlCommand getCmd() {
            return this.getCmd("gc");
        }

        public override Npgsql.NpgsqlCommand getCmd(string prefix) {
            NpgsqlCommand cmd = null;
            if(this.maxGeometriesRequired == nGeometriesRequired.zero) {
                cmd = new NpgsqlCommand();

                switch(this.conditionOperator) {
                    case Op.IS_NULL:    cmd.CommandText = String.Format("{0} IS NULL", this.column); break;
                    case Op.IS_CLOSED:  cmd.CommandText = String.Format("ST_IsValid({0})", this.column); break;
                    case Op.IS_EMPTY:   cmd.CommandText = String.Format("ST_IsEmpty({0})", this.column); break;
                    case Op.IS_RING:    cmd.CommandText = String.Format("ST_IsRing({0})", this.column); break;
                    case Op.IS_SIMPLE:  cmd.CommandText = String.Format("ST_IsSimple({0})", this.column); break;
                    case Op.IS_VALID:   cmd.CommandText = String.Format("ST_IsValid({0})", this.column); break;
                    default: throw new NotImplementedException();
                }
            } else if(this.maxGeometriesRequired == nGeometriesRequired.one) {
                if(this.geometry2compare == null) throw new Exception("Missing geometrySQL to compare. Use one of the add functions to add a geometrie to this condition");
                cmd = new NpgsqlCommand();
                //add geometrySQL to compare
                byte[] wkb = this.geometry2compare.AsBinary();
                String parameterName = String.Format("@{0}", prefix);
                cmd.Parameters.AddWithValue(parameterName, wkb);

                //basic geometrySQL to be compared with a tables column
                bool differingSRIDs = this.geometry2compare_srid != this.columnSRID;
                cmd.CommandText = String.Format("ST_GeomFromWKB({0},{1})", parameterName, this.geometry2compare_srid);
                
                //use a buffer around given geometrySQL?
                if(this.buffer_distance != 0) {
                    //buffer distance in units of input columnSRID
                    cmd.CommandText = String.Format("ST_Buffer({0},{1})", cmd.CommandText, this.buffer_distance);
                }

                //differing SRIDs? -> transorm into columnSRID of data bases tableName columns
                if(differingSRIDs) {
                    cmd.CommandText = String.Format("ST_Transform({0},{1})"
                            , cmd.CommandText, this.columnSRID);
                }

                //add condition
                switch(this.conditionOperator) {
                    case Op.DISJOINT: cmd.CommandText = String.Format("ST_Disjoint({0}, {1})", this.column, cmd.CommandText); break;
                    case Op.EUQALS: cmd.CommandText = String.Format("ST_Equals({0},{1})", this.column, cmd.CommandText); break;
                    case Op.HAS_ARC: cmd.CommandText = String.Format("ST_HasArc({0},{1})", this.column, cmd.CommandText); break;
                    case Op.INTERSECTS: cmd.CommandText = String.Format("ST_Intersects({0},{1})", this.column, cmd.CommandText); break;
                    case Op.IS_WITHIN: cmd.CommandText = String.Format("ST_Within({0},{1})", this.column, cmd.CommandText); break;
                    case Op.OVERLAPS: cmd.CommandText = String.Format("ST_Overlaps({0},{1})", this.column, cmd.CommandText); break;
                    case Op.TOUCHES: cmd.CommandText = String.Format("ST_Touches({0},{1})", this.column, cmd.CommandText); break;
                    default: throw new NotImplementedException();
                
                }
            }
            if(this.negation) cmd.CommandText = "NOT " + cmd.CommandText;
            
            return cmd;
        }

        public override bool IsEmpty {
            get { return this.maxGeometriesRequired == nGeometriesRequired.zero || 
                 (this.maxGeometriesRequired == nGeometriesRequired.one && this.geometry2compare == null); }
        }

       // #endregion
    }
}
